package com.lagou.edu.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lagou.edu.service.UserService;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Setter
@Getter
@RefreshScope
@Slf4j
@Component  // 让容器扫描到，等同于注册了
public class LoginFilter implements GlobalFilter, Ordered {

    private final static String cookieKey = "lagou_cookie";
    private static List<String> whiteList = new ArrayList<>();
    private final static String loginUrl = "/api/user/login";

    static {
        whiteList.add("/api/code");
        whiteList.add("/api/user/register");
    }
    // http://www.test.com:9006/master/lagou-cloud-gateway-dev.yml
    // curl -X POST http://www.test.com:9006/actuator/bus-refresh
    ConcurrentHashMap<String, Entry> map = new ConcurrentHashMap<>();
    @Value("${lagou.timeout:5}")
    private Integer timeout;
    @Value("${lagou.limitcount:5}")
    private Integer limitcount;
    @Autowired
    private UserService userService;

    @Scheduled(fixedRate = 1000)
    public void task() {
        Set<Map.Entry<String, Entry>> entries = map.entrySet();
        Iterator<Map.Entry<String, Entry>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Entry> next = iterator.next();
            Entry value = next.getValue();
            int timeout = value.getTimeout();
            if (timeout == 0) {
                map.remove(next.getKey());
            } else {
                next.getValue().setTimeout(timeout - 1);
            }
        }

    }

    boolean isContains(String string) {
        return whiteList.stream().anyMatch(string::contains);
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 限制时间内访问次数
        if (limitIpConut(request)) {
            // 303
//            return return401Mono(response,HttpStatus.SEE_OTHER,"您频繁进⾏注册，请求已被拒绝");
            return return401Mono(response,HttpStatus.SEE_OTHER,"您频繁请求，请求已被拒绝");
        }


        String path = request.getPath().toString();
        if (path.contains(loginUrl)) {
            // 成功返回设置cookie
            return chain.filter(exchange);
        }

        // 路径在白名单
        if (isContains(path)) {
            return chain.filter(exchange);
        }

        if (request.getCookies().isEmpty() || !request.getCookies().containsKey(cookieKey)) {
            return return401Mono(response,HttpStatus.UNAUTHORIZED,"拒绝访问");
        }

        // cookie校验失败
        String value = Objects.requireNonNull(request.getCookies().getFirst(cookieKey)).getValue();
        if (!userService.validToken(value)) {
            return return401Mono(response,HttpStatus.UNAUTHORIZED,"拒绝访问");
        }
        return chain.filter(exchange);
    }

    private boolean limitIpConut(ServerHttpRequest request) {
        String clientIp = request.getRemoteAddress().getHostString();
        Entry entry = map.get(clientIp);
        if (entry != null) {
            entry.setCount(entry.getCount() + 1);
        } else {
            Entry value = new Entry();
            value.setTimeout(getTimeout());
            value.setLimitcount(getLimitcount());
            value.setCount(1);
            map.put(clientIp, value);
        }
        entry = map.get(clientIp);
        // 超过限制
        if (entry.getCount() > entry.getLimitcount()) {
            return true;
        }
        return false;
    }

    ObjectMapper objectMapper = new ObjectMapper();

    private Mono<Void> return401Mono(ServerHttpResponse response,HttpStatus status, String... msg) {
        response.setStatusCode(status); // 状态码
        response.getHeaders().set("Content-Type","application/json;charset=UTF-8");
        String data = "Request be denied!";
        if (!ObjectUtils.isEmpty(msg)) {
            data = msg[0];
        }
        RuntimeException runtimeException = new RuntimeException(data);
        try {
           data = objectMapper.writeValueAsString(runtimeException);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
        return response.writeWith(Mono.just(wrap));
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Data
    public static class Entry {
        private int count;
        private int limitcount;
        private int timeout;
    }
}
